package org.vacoor.nothing.security.lucene;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrServer;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.LBHttpSolrServer;
import org.apache.solr.client.solrj.response.*;
import org.apache.solr.client.solrj.util.ClientUtils;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.util.NamedList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

/**
 * @author vacoor
 * @beta
 */
public class SolrGoodsSearchServiceImpl implements GoodsSearchService {
    private static final Logger LOG = LoggerFactory.getLogger(SolrGoodsSearchServiceImpl.class);

    public static final String MIX_VAL_DELIMITER = "::";        // 混合值分隔符  123::中文::345::语言
    public static final String CAT_FIELD = "cat";               // 分类字段
    public static final String CAT_VAL_PREFIX = CAT_FIELD + "_";
    public static final String BRAND_FIELD = "brand";           // 品牌字段
    public static final String BRAND_VAL_FIELD = "brandName";   // 品牌字段
    public static final String PRICE_FIELD = "price";           // 价格字段
    public static final String DYN_PROPS_FIELD = "props";       // 动态属性列表字段
    public static final String DYN_PROP_PREFIX = "prop_";
    public static final String DYN_PROP_VAL_PREFIX = "prop_v_";
    public static final String SORT_DELIMITER = "-";

    private SolrServer solrServer;
    private boolean catStrict = true;

    public SolrGoodsSearchServiceImpl() {
        String[] urls = {
//                "http://127.0.0.1:8888/noahs",
//                "http://127.0.0.1/solr/noahs",
//                "http://192.168.1.123:8080/solr/noahs",
//                "http://121.41.31.94/solr/noahs"
                "http://127.0.0.1:8888/jiusu"
                // "http://192.168.1.123:8080/solr/jiusu"
        };
        solrServer = new LBHttpSolrServer(null, urls);
    }

    /**
     * !!create 2014.11.19
     * 商品筛选说明:
     * <p/>
     * 属性筛选的入口是分类, 只有选择了分类才允许进行属性筛选
     * 1. 如果没有选择分类, 大多数做法是尝试获取最佳匹配分类,(匹配度大于某个值?具体算法不清楚, 好像都会优先匹配分类)
     * 无法匹配最佳分类, 则只允许通过 category, brand, price 进行筛选
     * 2. 当存在具体分类(匹配到或给定)， 则在上面基础上允许通过商品属性筛选
     * <p/>
     * <pre>
     * 设计:
     * 1. 对于动态属性的索引存储结构
     *    key:   prop_{prop_id}      prop_v_{prop_value_id}
     *    value:{prop_value_id}        {prop}_{prop_value}
     *    -------------------------------------------
     *    eg:
     *    property
     *      id      name
     *      41     颜色
     *    --
     *    property value
     *      id     name
     *     4101    蓝色
     *    则存储
     *    key:   prop_41      prop_v_4101
     *    value:  4101         颜色_蓝色
     *
     *   当 facet 颜色时 facet.field = prop_41
     *   对于每一个 facet value 获取其 name, 及 prop_{prop_id}字段的值 -- prop_value_id
     *   获取 prop_v_{name} (prop_v_4101) 值 即为 中文内容 (颜色_蓝色)
     *   切割后 即为 facet field 中文和 facet value 中文
     *
     * 2. 因为属性都是动态的, 因此需要动态 facet, 如何获取一共有哪些属性 facet (solr 不支持 addFacetField('prop_*')
     *    这里增加 props 存放每条记录的所有属性, 以便通过 facet props 来获取所有需要 facet 的属性
     *    props 的值存放可以使用以下两种方式:
     *    1. multiValued, facet props 后直接得到所有需要 facet 的属性
     *    2. "," 分隔存放所有属性, 获取后分隔去重获得所有需要 facet 的属性
     *    这里选取 multiValued
     *
     * 3. 分类的确定及属性显示
     *    当搜索"锤子", 会涉及 五金, 手机等分类, 存在两个问题
     *    1: 显示所有商品的属性, 显然不合理 (我肯定只想买某个类别下的东西:五金或锤子手机), 显示所有的属性显然不好
     *    2: 对所有涉及属性 facet (可能很多) 效率低
     *
     *    综上, 应该对于输入的查询参数,应该首先找到最佳匹配的分类, facet 并显示该分类所有属性即可
     *    对于最佳匹配分类的各项权重可能不同, 可能需要考虑完全匹配程度, 搜索热度等
     *    例如, 锤子手机的搜索热度可能要大于五金下的锤子
     *    另外, 只有当最佳匹配的分类的匹配度大于某个值时才显示其属性
     *    其他情况都只显示最通用的属性:分类,品牌,价格
     *
     *    这样有以下好处:
     *    1. 减少 facet 字段
     *    2. 对于用户也没有问题, 搜索的实在太模糊了无法想找哪一类东西, 应该进一步选择分类
     * </pre>
     * -------------------------------------------------------------------
     * !!update 2014.11.20 更改 facet --> group
     * 实际中并不关心每个可筛选条件下有多少数量, 将 facet 修改为 group, 减少查询次数
     * -------------------------------------------------------------------
     * !!update 2014.11.28 修改分类为虚拟分类
     * -----------
     * 一个商品可以存在于多个虚拟分类下
     * 虚拟分类:商品 = 1:n
     * 简单起见, 直接修改 cat 为 multiValued field
     * 存在问题:
     * 1. group field 不能是 multiValued field, 无法获取所有分类
     * 2. 分类树形结构将不能再使用, facet.pivot
     * 改用 facet 来处理分类, 并修改结构
     * 结构变更如下
     * <pre>
     * cat  cat_name pcat  pname            cat                cat_{cat_id_1}                   cat_{cat_id_2}
     * 123    手机    12    电子     -->  {cat_id_1}     {cat_text}:{pcat_id}_{pcat_text}       数码:12:电子
     *                                    {cat_id_2}
     * </pre>
     * 此时: <br>
     * 1. 获取所有分类使用: addFacetField("cat")
     * 2. 获取分类名称及父分类: 获取cat_{cat_id}值, split(":")
     * 请求: <br>
     * 1. 过滤 + facet(分类)
     * 2. 如果存在分类, 获取该分类的所有属性
     * 3. group (2中属性, 品牌)
     * 4. 对每个 facet 获取 名称和父分类
     * 共计: 2 + facet value count ~  3 + facet value count 次
     * Q: 搜索父分类时, 子分类下的商品依然不会出现在结果中(搜索父分类时, 子分类下的商品依然不会出现在结果中)
     * A: 已经解决, 看 2014.12.01 更新
     * -------------------------------------------------------------------
     * !!update 2014.12.01 增加车系车型
     * ----------
     * 品牌:产地:车系:排量:车型
     * 品牌:产地 = 1:n
     * 产地:车系 = 1:n
     * 车系:车型 = 1:n (排量对车型的归类, 因此 车系:排量 = 1:n, 排量:车型 = 1:n)
     * 需要进行一下筛选:
     * 1. 品牌来筛选商品
     * 2. 产地筛选商品
     * 3. 车系筛选商品
     * 4. 排量筛选商品
     * 5. 车型筛选商品
     * 索引结构:
     * 索引中一条记录是一个商品, 如果品牌,产地,车系每个都使用一个field
     * <pre>
     *      brand        place      series    ... {place_id}_text    {series_id}_text
     *   {brand_id}   {place_id}  {series_id}      大众_一汽大众     大众_一汽大众_CC
     * </pre>
     * 以上必须保证每个字段都是 multiValued field, 且每一个 fieldValue 需要额外字段存储值
     * 例如, 上面 place_b1_text 存储 b1 的文本内容(品牌_产地), series_s1_text(品牌_产地_车系)
     * 另外需要根据用户传递类型来找特定的字段
     * 优化:
     * 因为在层级关系中, 上级与下级关系都是 1:n, 因此可以认为所有的上级都是下级的一种, 即 品牌,产地,车系都是车型
     * <pre>
     *    model
     *    品牌
     *    品牌_产地
     *    品牌_产地_车系
     * </pre>
     * 如果下级的标识在只在上级内唯一, 则使用两者合并作为key, 否则则可以直接使用其标识
     * <pre>
     *    model       model_{id_1}       model_{id_2}       model_{id_3}
     *    品牌            大众           大众_一汽大众    大众_一汽大众_CC
     *    产地
     *    车系
     *    model       model_1    model_1212
     *     12           大众    大众_一汽大众
     *     1212
     * </pre>
     * 此时, 不论用户传递的是何种都可以直接到 model 查找
     * 请求: <br>
     * 请求: <br>
     * 1. 过滤 + facet(分类) + model (如果存在)
     * 2. 如果存在分类, 获取该分类的所有属性
     * 3. group (2中属性, 品牌)
     * 4. 对每个 facet 值 获取 名称和父分类/父model
     * 共计: 2 + facet value count ~  3 + facet value count 次
     * 这里其实仅增加了 facet value 的数量
     * !! 这次 facet 也是可以省略的, 因为仅仅需要在有结果时获取系那是内容即可, results[0].['model_' + modelId]
     * !! 就这样子
     * ---------------
     * 1. 思考前面的动态属性处理, 如果使用如下存储结构
     * <pre>
     *          props
     *   prop_key:prop_value_key
     *   prop_key:prop_value_key
     * </pre>
     * 这样子不方便获取共有哪些属性, 及每个属性有哪些值, 放弃
     * 2. 思考前面分类中, "搜索时, 子分类商品如何出现在父分类中" 问题
     * 如果也将父分类认为是子分类的一种, 即电子也是数码, 结构如下
     * <pre>
     *   cat          {cat_id}_text                     cat_mobile_text      cat_digital_text
     *  mobile      {cat_text}:{pcat_id}:{pcat_text}    手机:digital:数码         数码::
     *  digital
     * </pre>
     * 1. 只要将父子都存在 cat, 即可"处理子商品出现在父分类下问题"
     * 2. 父子关系可以通过 cat_text 获取
     * 3. 这样子顺带虚拟分类也是这样存储的
     * TODO 就这样子做
     *
     * @param kw     搜索关键字
     * @param cat    商品分类
     * @param brand  商品品牌
     * @param ppaths 商品属性路径, 格式 属性key:属性值:key
     * @param offset 记录偏移量
     * @param rows   记录总数
     * @param orders 排序, 格式 field-dir, eg: price-desc
     * @return
     * @throws SolrServerException
     */
    @Override
    public Map<String, Object> search(String kw, String cat, String brand, Double sPrice, Double ePrice, String[] ppaths, int offset, int rows, String[] orders) {
        Map<String, Object> ret = Maps.newHashMap();
        ppaths = null != ppaths ? ppaths.clone() : new String[0];
        orders = null != orders ? orders.clone() : new String[0];
        boolean hasKeyword = StringUtils.hasText(kw);
        boolean hasCate = StringUtils.hasText(cat);

        // 没有任何可搜索条件
        if (!hasCate && !hasKeyword && 1 > ppaths.length) {
            // throw new IllegalArgumentException("no query condition found! (keyword or category or brand or property path)");
        }

        offset = 0 > offset ? 0 : offset;
        rows = 0 > rows ? 0 : rows;
        // 转义Solr 特殊字符: * + – && || ! ( ) { } [ ] ^ " ~ * ? : \ 空格 (空格表示 OR),
        kw = hasKeyword ? ClientUtils.escapeQueryChars(kw) : "*:*";
        List<String> groupFields = Lists.newArrayList();

        try {
            final SolrQuery query = new SolrQuery(kw).setFacet(true)
                    .setStart(offset)
                    .setRows(rows)
                    .setIncludeScore(true); // 包含匹配打分

            // 排序处理, 格式 {field}-{dir}
            for (String order : orders) {
                if (StringUtils.hasText(order)) {
                    String[] sort = order.split(Pattern.quote(SORT_DELIMITER));
                    String field = sort[0];
                    String dir = sort[sort.length - 1];
                    query.addSort(field, "desc".equals(dir) ? SolrQuery.ORDER.desc : SolrQuery.ORDER.asc);
                }
            }

            // 品牌筛选
            groupFields.add(BRAND_FIELD);
            if (StringUtils.hasText(brand)) {
                query.addFilterQuery(BRAND_FIELD + ":" + ClientUtils.escapeQueryChars(brand));
            }

            // 价格区间筛选
            if (null != sPrice && null != ePrice) {
                query.addFilterQuery(String.format("%s:[%s TO %s]", PRICE_FIELD, sPrice, ePrice));
            }

//            Map<String, NamedKV<String, NamedKV<String, ?>>> filterProps = Maps.newHashMap();
            // 属性筛选
            for (int i = 0; i < ppaths.length; i++) {
                // TODO 查询单独拉出来 map.put()  map.get().setName()
                if (StringUtils.hasText(ppaths[i])) {
                    query.addFilterQuery(DYN_PROP_PREFIX + ppaths[i]);
                }
            }

            // TODO 查找最佳分类, 需要优化
            // 不包含分类
            // 1. 首先, 存在关键字,尝试获取名称完全匹配的分类
            /*
            // @update 2014.11.28 更新分类可以有多个(虚拟分类), 暂时禁用, TODO 现在肿么去实现？
            if (!hasCate && hasKeyword) {
                SolrQuery tryCateQuery = new SolrQuery("*:*").addFilterQuery("catName:" + kw)//.addFilterQuery("cat:" + kw)
                        .setStart(0).setRows(1).setIncludeScore(true);
                SolrDocumentList results = solrServer.query(tryCateQuery).getResults();

                if (0 < results.size()) {
                    cat = (String) results.get(0).get("cat");
                    hasCate = StringUtils.hasText(cat);
                }
            }
            */

            // 2. 其次, 尝试根据关键字和其他搜索条件来确定分类
            if (!hasCate) {
                SolrQuery tryCateQuery = new SolrQuery(kw).setFilterQueries(query.getFilterQueries())
                        .setFacet(true)
                        .addFacetField(CAT_FIELD)
                        .setRows(1);
                QueryResponse tryCateResp = solrServer.query(tryCateQuery);
                FacetField facetCat = tryCateResp.getFacetField(CAT_FIELD);
                SolrDocumentList results = tryCateResp.getResults();

                // 2.1 遍历所有分类, 尝试获取唯一匹配的分类
                String tCat = null;
                int catCount = 0;   // 包含匹配的内容的分类数
                for (FacetField.Count count : facetCat.getValues()) {
                    if (0 < count.getCount()) {     // 至少一个商品
                        catCount++;
                        tCat = count.getName();
                    }
                    // 多于一个分类, 肯定不符合了, 没必要继续
                    if (1 < catCount) {
                        break;
                    }
                }

                if (1 == catCount) {  // 只有一个分类, 分类就是这个分类
                    cat = tCat;
                } else if (0 < results.size()) {
                    // 2.2  使用关键字匹配最佳分类, 这里暂时取查询到的第一条记录(默认应该是按照打分排序的) TODO
                    // cat = (String) results.get(0).get("cat");
                }
                hasCate = StringUtils.hasText(cat);
            }

            query.addFacetField(CAT_FIELD);

            // 包含分类, 添加对分类下属性的分组
            if (hasCate) {
                query.addFilterQuery(CAT_FIELD + ":" + ClientUtils.escapeQueryChars(cat));  // 添加分类过滤

                // 通过 facet 获取所有属性
                SolrQuery propsQuery = new SolrQuery(query.getQuery()).setFilterQueries(query.getFilterQueries())
                        .setFacet(true)
                        .setRows(0)
                        .addFacetField(DYN_PROPS_FIELD);
                QueryResponse propsResp = solrServer.query(propsQuery);
                FacetField props = propsResp.getFacetField(DYN_PROPS_FIELD);
                for (FacetField.Count count : props.getValues()) {
                    String prop = count.getName();
                    long size = count.getCount();
                    // fq = "{!tag=aa}" + fq;  // 给 fq 添加 tag = aa, 用于 facet 时排除该 filter
                    // propKey = "{!ex=aa}" + propKey; facet 时排除 tag = aa 的 filter
                    if (0 < size) {
                        groupFields.add(DYN_PROP_PREFIX + prop); // 添加属性分组
                    }
                }
            } else if (catStrict) {
                // 只有有分类才显示, 当没有分类默认为全部分类
                query.addFilterQuery(CAT_FIELD + ":*");
            }

            final QueryResponse response = solrServer.query(query);

            // 获取当前条件下的分类信息
            Map<String/*cat id*/, NamedKV<String, NamedKV<String, ?>>/*cat*/> allCats = Maps.newHashMap();
            Map<String/*cat id*/, NamedKV<String, NamedKV<String, ?>>/*cat*/> pCats = Maps.newHashMap();
            List<FacetField.Count> values = response.getFacetField(CAT_FIELD).getValues();  // 所有的分类 id
            for (FacetField.Count value : values) {
                String catId = value.getName();

                if (1 > value.getCount()) { // 该维度没有值
                    continue;
                }

                /**
                 * 查询该分类 id 的名称及父名称
                 * 存储为
                 *   cat        cat_[cat_id]
                 * [cat id]     [cat name]:[parent id]:[parent name]
                 */
                String catTextKey = CAT_VAL_PREFIX + catId;
                SolrQuery catQuery = new SolrQuery(catTextKey + ":*").setRows(1);  // only one
                SolrDocumentList results = solrServer.query(catQuery).getResults();
                if (0 < results.size()) {
                    SolrDocument doc = results.get(0);
                    // [cat name]:[parent id]:[parent name]
                    String text = (String) doc.get(catTextKey);
                    String[] pairs = text.split(Pattern.quote(MIX_VAL_DELIMITER));

                    NamedKV<String, NamedKV<String, ?>> c = NamedKV.create(catId, pairs[0]);
                    allCats.put(catId, c);

                    if (2 < pairs.length) {
                        NamedKV<String, NamedKV<String, ?>> p = allCats.get(pairs[1]);
                        if (null == p) {
                            p = NamedKV.create(pairs[1], pairs[2]);
                            allCats.put(p.getKey(), p);
                            // pCats.put(p.getKey(), p);
                        }
                        p.addValue(c);
                        pCats.put(p.getKey(), p);
                    }
                }
            }

            // 查询分组信息
            SolrQuery groupQuery = new SolrQuery(query.getQuery()).addFilterQuery(query.getFilterQueries())
                    .setParam("group", true)
                    .setParam("group.limit", "1")   // 每个分组内返回1条记录(每个品牌下返回几条记录)
                    .setParam("group.field", groupFields.toArray(new String[groupFields.size()]))
                    .setRows(1024); // 每个分组最多返回1024个值(eg: 对品牌分组最多返回多少个品牌)
            GroupResponse groupResp = solrServer.query(groupQuery).getGroupResponse();

            // 存放转换后的所有分组属性
            Map<String/*key*/, NamedKV<String, NamedKV<String, ?>>/*props*/> groupedProps = Maps.newHashMap();
            for (GroupCommand fieldGroup : groupResp.getValues()) {  // 遍历所有分组信息
                String groupName = fieldGroup.getName();    // 分组字段名
                List<Group> groups = fieldGroup.getValues();

                NamedKV<String, NamedKV<String, ?>> namedProp = null;
                for (Group g : groups) {                    // 遍历 groupName 所有分组
                    String groupValue = g.getGroupValue();  // 组名称, eg: 三星
                    SolrDocumentList docs = g.getResult();  // 该组下的文档

                    if (null == groupValue) {
                        continue;
                    }

                    if (0 < docs.size()) {
                        SolrDocument doc = docs.get(0);
                        if (BRAND_FIELD.equals(groupName)) {
                            String brandName = (String) doc.get(BRAND_VAL_FIELD);

                            if (null == namedProp) {
                                namedProp = NamedKV.create(BRAND_FIELD, BRAND_FIELD);
                            }
                            namedProp.addValue(NamedKV.create(groupValue, brandName));
                            continue;
                        }

                        // 如果是以属性进行分组 (groupName 格式为 prop_{prop_id})
                        if (groupName.startsWith(DYN_PROP_PREFIX)) {
                            // 格式: {prop_name}_{prop_value_name}
                            String text = (String) doc.get(DYN_PROP_VAL_PREFIX + groupValue);
                            String[] pairs = text.split(Pattern.quote(MIX_VAL_DELIMITER));
                            String prop = pairs[0];                    // 属性名称
                            String value = pairs[pairs.length - 1];    // 属性值名称
                            String propKey = groupName.substring(DYN_PROP_PREFIX.length());

                            if (null == namedProp) {
                                namedProp = NamedKV.create(propKey, prop);
                            }
                            namedProp.addValue(NamedKV.create(groupValue, value));
                        }
                    }
                }
                if (null != namedProp) {
                    groupedProps.put(namedProp.getKey(), namedProp);
                }
            }

            ret.put("cat", allCats.get(cat));       // 当前搜索的分类
            ret.put("pCats", pCats);                // 所有父分类
            ret.put("allCats", allCats);            // 所有分类
            ret.put("groupedProps", groupedProps);  // 所有属性分组   (包括品牌)
            ret.put("results", response.getResults());  // 搜索结果

            // 擦, 额外翻译下字段
            List<NamedPropertyPath> namedPPaths = Lists.newArrayList();
            for (String ppath : ppaths) {
                String[] kv = ppath.split(Pattern.quote(":"));
                NamedKV<String, NamedKV<String, ?>> namedProp = groupedProps.get(kv[0]);

                if (null == namedProp) {
                    continue;
                }
                for (NamedKV<String, ?> value : namedProp.getValues()) {
                    if (kv[kv.length - 1].equals(value.getKey())) {
                        namedPPaths.add(new NamedPropertyPath(ppath, namedProp.getName() + ":" + value.getName()));
                        break;
                    }
                }
            }
            ret.put("ppaths", namedPPaths);

            // 简单的还可以, 复杂的还得自己来
            // List<IndexedGoods> beans = response.getBeans(IndexedGoods.class);
        } catch (SolrServerException sse) {
            sse.printStackTrace();
            LOG.error("invoke solr server error", sse);
        }

        return ret;
        /*
        SolrDocumentList hitDocs = response.getResults();
        Float maxScore = hitDocs.getMaxScore();
        long totalHits = hitDocs.getNumFound();
        System.out.println(String.format("共命中: %s %s", totalHits, maxScore));

        for (int i = 0; i < hitDocs.size(); i++) {
            SolrDocument hitDoc = hitDocs.get(i);
            System.out.println(String.format("%s 命中: %s", i, hitDoc));
        }
        */
    }

    public void findMoreLikeThis() throws SolrServerException {
        /**
         * 不需要修改以下配置
         * conf/solrconfig.xml
         * &lt;requestHandler name="/mlt" class="solr.MoreLikeThisHandler" /&gt;
         */
        SolrQuery mltQuery = new SolrQuery();
        mltQuery.setQuery("*:*")
                .addFilterQuery("id:068E39B280F40EB4E050A8B4AF29413E")
                .addFilterQuery("price:133.8")
//             .setParam("fl", "id,title,score")
                .setParam("mlt", true)         // 开启 mlt
                .setParam("mlt.fl", "title")   // mlt 字段
                .setParam("mlt.mindf", "1")
                .setParam("mlt.maxtf", "1")
                .setParam("mlt.count", "10") // 每个结果检索的文档数
                .setRows(1);   // 结果数

        QueryResponse response = solrServer.query(mltQuery);
        SolrDocumentList docs = response.getResults();
        for (SolrDocument doc : docs) {
//            System.out.println(doc.get("id") + "," + doc.get("title"));
            System.out.println(doc);
        }
        NamedList<SolrDocumentList> dds = (NamedList<SolrDocumentList>) response.getResponse().get("moreLikeThis");
        SolrDocumentList docs1 = dds.get("068E39B280F40EB4E050A8B4AF29413E");
        for (SolrDocument doc : docs1) {
            System.out.println(doc);
        }
    }

    public static class NamedPropertyPath {
        private final String path;
        private final String name;

        public NamedPropertyPath(String path, String name) {
            this.path = path;
            this.name = name;
        }

        public String getPath() {
            return path;
        }

        public String getName() {
            return name;
        }
    }

    /* *********************************
     *        getter / setter
     * *********************************/

    public SolrServer getSolrServer() {
        return solrServer;
    }

    public void setSolrServer(SolrServer solrServer) {
        this.solrServer = solrServer;
    }

    public static class NamedKV<K, V> {
        private final K key;
        private final String name;
        private List<V> values;

        public static <K, V> NamedKV<K, V> create(K key, String name) {
            return new NamedKV<K, V>(key, name);
        }

        public static <K, V> NamedKV<K, V> create(K key, String name, List<V> values) {
            return new NamedKV<K, V>(key, name, values);
        }

        public NamedKV(K key, String name) {
            this(key, name, null);
        }

        public NamedKV(K key, String name, List<V> values) {
            this.key = key;
            this.name = name;
            this.values = values;
        }

        public K getKey() {
            return key;
        }

        public String getName() {
            return name;
        }

        public List<V> getValues() {
            values = null == values ? Lists.<V>newArrayList() : values;
            return values;
        }

        public NamedKV addValue(V v) {
            getValues().add(v);
            return this;
        }
    }

    public static void main(String[] args) {
        SolrGoodsSearchServiceImpl solr = new SolrGoodsSearchServiceImpl();
        Map<String, Object> search = solr.search(null, null, null, null, null, null, 0, 30, null);
        Map<String, NamedKV<String, NamedKV<String, ?>>> groupedProps = (Map<String, NamedKV<String, NamedKV<String, ?>>>) search.get("groupedProps");
        for (NamedKV<String, NamedKV<String, ?>> namedProp : groupedProps.values()) {
            System.out.printf("%s %s\n", namedProp.getName(), namedProp.getName());

            for (NamedKV<String, ?> value : namedProp.getValues()) {
                System.out.printf("\t%s %s", value.getName(), value.getKey());
            }
        }
        SolrDocumentList docs = (SolrDocumentList) search.get("results");
        for (SolrDocument doc : docs) {
            System.out.println(doc);
        }
    }
}
